Durante el proceso de análisis de datos, uno de los recursos más útiles que nos brinda la programación es la capacidad de “iterar” a gran velocidad: escribir código que se encargue de tareas que serían tediosas de resolver “a mano”, y una vez aplicado para un caso en particular volver a usarlo en otros contextos con mínimo esfuerzo adicional. De este modo podemos evaluar distintos aspectos de nuestros datos (identificar valores extremos, calcular métricas agregadas por categoría, realizar modelos estadísticos, obtener gráficos que muestren relaciones, etc) sin que importe tanto si son decenas o millones de observaciones, o si lidiamos con un dataset o con veinte. La gracia de usar código es que permite una automatización de tareas que multiplica nuestra capacidad de análisis, y además nos evita el fastidio de tener que realizar tareas repetitivas… ¡que se encargue la computadora!.
Como si eso fuera poco, existe otro recurso que también permite analizar grandes cantidades de información y sacar conclusiones en forma rápida, que habilita la programación: la visualización interactiva. Así como hemos aprendido a realizar gráficos estáticos, aprendiendo algunos trucos adicionales podremos generar versiones dinámicas, que permiten a la audiencia interactuar con los datos para revisar sus atributos, cambiar las variables comparadas, o “hacer zoom” en distintas áreas para mostrar subconjuntos mas pequeño o más grande de los datos disponibles.
Una buena visualización interactiva nos permite “interrogar” a los datos de forma intuitiva, explorándolos de acuerdo a las preguntas que nos surgen al verlos en pantalla. Organizar y reorganizar los datos de forma visual puede ayudar a descubrir patrones y relaciones clave que serían difíciles de discernir al verlos en una tabla o incluso en una visualización estática.
También puede entusiasmarnos compartir una visualización interactiva para hacer accesible ésta capacidad de análisis rápido a un público amplio, invitado a jugar con los datos por su cuenta. Aquí vale aclarar que la audiencia casual rara vez siente ganas de dedicar su tiempo a mover diales para explorar los datos que presentamos. En general una visualización estática, que vaya directo al grano y muestre alguna conclusión, es preferible a una opción interactiva. A pesar de bajar la barrera de entrada al análisis rápido de datos, la interactividad podría ser mucho mas útil para analistas con experiencia que para su audiencia… al menos por ahora.
Uno de los fuertes de R es sin dudas la calidad y
cantidad de herramientas de visualización disponibles. Además de las
funciones incluidas en el lenguaje, y de la “gramática” para realizar
todo tipo de gráficos que ofrece ggplot2, podemos agregar a
nuestro repertorio paquetes adicionales que realizan visualizaciones
específicas. En la vertiente de interactividad, son notables los
paquetes reunidos bajo el nombre de htmlwidgets, que generan
visualizaciones dinámicas con una gran variedad de estilos y
recursos.
La particularidad de los htmlwidgets (palabra que yo
traduciría como “cositos HTML”) es que traen a R excelentes
funciones de visualización desarrolladas en otro idioma:
JavaScript. El gran fuerte de JavaScript es la
generación de contenido interactivo para sitios web. Los paquetes
reunidos en la colección htmlwidgets “envuelven” el código
Javascript en instrucciones en R, haciendo un
puente entre los dos mundos, y permitiendo generar desde R
visualizaciones que pueden publicarse como contenido web. Para nuestros
fines, vamos a concentrarnos en una de las opciones en particular: Plotly. Esto debido a que
Plotly permite convertir nuestros gráficos realizados con
ggplot2 en versiones interactivas, con solo agregar una
línea de código. Dicho de otra forma: podemos producir visualizaciones
interactivas… ¡sin necesitar nada más que lo que ya hemos
aprendido!.
Vamos a demostrarlo con un ejemplo.
Traigamos los datos de Gapminder que ya hemos usado antes:
head(gapminder)
pais continente año expVida pobl PBIpc
1 Afghanistan Asia 1952 28.801 8425333 779.4453
2 Afghanistan Asia 1957 30.332 9240934 820.8530
3 Afghanistan Asia 1962 31.997 10267083 853.1007
4 Afghanistan Asia 1967 34.020 11537966 836.1971
5 Afghanistan Asia 1972 36.088 13079460 739.9811
6 Afghanistan Asia 1977 38.438 14880372 786.1134
Los paquetes que solemos usar:
library(ggplot2)
library(dplyr)
Y recordemos el código que habíamos usado para crear una visualización como las de Hans Rosling:
color_continentes <- c("Europe" = "darkorange", "Asia" = "red", "Africa" = "blue",
"Americas" = "yellow", "Oceania" = "purple")
ggplot(filter(gapminder, año == 2007),
aes(x = PBIpc, y = expVida, size = pobl/1000000, color = continente)) +
geom_point() +
scale_x_log10() +
scale_colour_manual(values = color_continentes) +
guides(size = "none") +
theme_minimal() +
labs(title = "Riqueza vs. salud en los países del mundo", subtitle = "según datos 2007",
size = "población (millones)",
x = "PBI per capita (USD)", y = "expectativa de vida en años",
caption = "fuente: Gapminder, www.gapminder.com")
Para realizar una versión interactiva del mismo gráfico, activamos el
paquete plotly
library(plotly)
Ahora solo necesitamos dos cosas:
ggplotly, que la convertirá en una versión
interactiva.Y eso es todo. A intentarlo:
p <- ggplot(filter(gapminder, año == 2007),
aes(x = PBIpc, y = expVida, size = round(pobl/1000000,2), color = continente)) +
geom_point() +
scale_x_log10() +
scale_colour_manual(values = color_continentes) +
guides(size = "none") +
theme_minimal() +
labs(title = "Riqueza vs. salud en los países del mundo", subtitle = "según datos 2007",
size = "población (millones)",
x = "PBI per capita (USD)", y = "expectativa de vida en años",
caption = "fuente: Gapminder, www.gapminder.com")
ggplotly(p)
Si todo salió bien, debería aparecer una visualización en pantalla
muy similar al que hicimos con ggplot2… hasta que pasamos
el puntero del mouse por encima. Ahí se hace evidente que esta versión
es interactiva y permite, entre otras cosas:
Por defecto, la “tooltip” muestra las variables que representan los
atributos estéticos que asginamos al crear el ggplot, dentro de
alguna llamada a aes() (“x”, “y”, “color”, etc). para
controlar cuales aparecen, podemos usar el parámetro “tooltip”. Para que
sólo se muestren PBI y expectativa de vida, que hemos asignado a \(x\) e \(y\), usaríamos
ggplotly(p, tooltip = c("x", "y")). Algo que suele ser útil
es usar la tooltip para mostrar en el recuadro valores que no
hemos representado visualmente, y así poder incluir información.
Podríamos querer que se muestre el país que representa cada punto ya que
es práctico poder indicar el país sólo para el punto que se elige, en
lugar de llenar la pantalla con etiquetas mostrando los nombres de todos
los países a la vez. Para eso “inventamos” un nombre de atributo
estético, por ejemplo “para_plotly” y le asignamos la variable que se
verá en la tooltip. ggplot() ignora los atributos estéticos
que no conoce (no hace nada con ellos), pero plotly() los
recibe y puede mostrar su valores en el recuadro emergente.
Como siempre, un ejemplo va a hacerlos más claro:
Asignamos la variable que queremos mostrar en la tooltip a un atributo estético ad-hoc, como “para_plotly”:
p <- ggplot(filter(gapminder, año == 2007),
aes(x = PBIpc, y = expVida, size = pobl/1000000, color = continente, para_plotly = pais)) +
geom_point() +
scale_x_log10() +
scale_colour_manual(values = color_continentes) +
guides(size = "none") +
theme_minimal() +
labs(title = "Riqueza vs. salud en los países del mundo", subtitle = "según datos 2007",
size = "población (millones)",
x = "PBI per capita (USD)", y = "expectativa de vida en años",
caption = "fuente: Gapminder, www.gapminder.com")
Y pedimos a plotly que use el atributo para la
tooltip:
ggplotly(p, tooltip = c("para_plotly"))
Y del mismo modo podemos obtener versiones interactivas de los otros
tipos de gráficos que sabemos hacer: de barras, boxplots, histogramas,
de densidad… y cualquier otro que pueda realizarse con
ggplot().
La animación es un recurso a veces complicado de implementar, pero muy poderoso para hacer que las datos “cuenten una historia”. Es sin duda una forma poderosa de comunicar, por su capacidad de llamar la atención de la audiencia y con ello lograr que nos dediquen los siguientes momentos de su día.
Si bien las herramientas para animar gráficos corresponden por
tradición al mundo del arte más que del análisis de datos, recientemente
ha aparecido una herramienta que brinda a R un marco de trabajo para
crear visualizaciones animadas. Se trata de gganimate, un paquete que extiende la
funcionalidad de ggplot2 y nos permite tomar como base las
visualizaciones que ya sabemos hacer, aplicando un conjunto de funciones
especializadas para obtener versiones animadas.
Es imposible mostrar en toda su variedad las formas en que podemos realizar animaciones, pero vamos a mostrar como emular dos estilos bastante conocidos: la carrera de gráfico de barras (que ya es una especie de género en si mismo, con sus detractores y´) y la animación de Gapminder, que vimos en la producción de la BBC para la primera clase de este curso.
Continuamos con nuestra querida data de Gapminder. ¿Qué tal si mostramos el incremento de la población a través de los años? Lo haremos con una “carrera” entre países, mostrando los que alcanzan mayor cantidad de habitantes en cada año registrado.
Continuando con la estrategia establecida en este curso,
aprovecharemos lo que ya sabemos hacer con ggplot2, y
construiremos sobre eso.
El primer ingrediente a resolver para la animación que tenemos en
mente es un gráfico de barras, que realizaremos con
ggplot(). Aquí tenemos que tomar algunas decisiones de
diseño:
Primer paso, transformar los datos. Para obtener una versión de los
datos que incluya sólo los 10 países con mayor población para cada año,
podemos usar las funciones de dplyr:
gapminder_ranking <- gapminder %>%
group_by(año) %>% # agrupa los datos según el valor de la columna "año"
arrange(año, desc(pobl)) %>% # ordena los miembros de cada grupo por población, de mayor a menor
mutate(ranking = row_number()) %>% # crea una columna numérica con el ranking
filter(ranking <= 10) # retiene solo el "top ten" y descarta las demás filas
Nos queda así:
gapminder_ranking
# A tibble: 120 × 7
# Groups: año [12]
pais continente año expVida pobl PBIpc ranking
<chr> <chr> <int> <dbl> <int> <dbl> <int>
1 China Asia 1952 44 556263527 400. 1
2 India Asia 1952 37.4 372000000 547. 2
3 United States Americas 1952 68.4 157553000 13990. 3
4 Japan Asia 1952 63.0 86459025 3217. 4
5 Indonesia Asia 1952 37.5 82052000 750. 5
6 Germany Europe 1952 67.5 69145952 7144. 6
7 Brazil Americas 1952 50.9 56602560 2109. 7
8 United Kingdom Europe 1952 69.2 50430000 9980. 8
9 Italy Europe 1952 65.9 47666000 4931. 9
10 Bangladesh Asia 1952 37.5 46886859 684. 10
# ℹ 110 more rows
Ahora, a preparar el gráfico. Como insumo para la animación,
necesitamos una visualización realizada con ggplot(). Hay
infinidad de maneras de abordar el desafío; tantas como formas de
mostrar poblaciones, países y año. En esta ocasión haremos un “facetado”
mostrando el ranking de población con facetas para cada año en el
dataset:
ggplot(gapminder_ranking) +
geom_col(aes(x = pobl / 1000000, y = factor(ranking), fill = continente)) +
geom_text(aes(x = pobl / 1000000, y = factor(ranking), label = pais)) +
scale_fill_manual(values = color_continentes) +
theme_minimal() +
facet_wrap(vars(año)) +
labs(y = NULL, x = "población (millones)")
Una vez que quedó como lo queremos, la animación se resuelve
reemplazando el facet_wrap() por una de las funciones de
gganimate que animan transiciones. Hay varias para elegir,
por ejemplo:
transition_states(): Anima transiciones entre “estados”
diferentes (por ejemplo, de “en proceso” a “despachado”)transition_time(): Anima transiciones entre momentos en
el tiempotransition_layers(): Anima un gráfico en “capas”, de
modo que vayan apareciendo en forma gradualY varias más. Son difíciles de describir, así que no hay mejor modo de entender la diferencia entre los métodos de animación que probarlos. Un buen punto para empezar es el tutorial de “primeros pasos” en el sitio oficial del paquete.
Con la data que tenemos entre manos, queda claro que vamos a usar
transition_states(), con la variable “año” dictando los
distintos momentos a mostrar. Para que el título del gráfico muestre un
texto distinto dependiendo del año en cada momento de la animación,
usaremos un truco que aporta gganimate: Si en el texto
asignado al título incluimos {frame_time}, ese indicador será
reemplazado por el valor de la variable de tiempo según el punto de la
animación.
Veanse las dos últimas líneas, que contienen el código nuevo, y el resultado:
gapminder_anim1 <- ggplot(gapminder_ranking, aes(group = pais)) +
geom_col(aes(x = pobl / 1000000, y = factor(ranking), fill = continente)) +
geom_text(aes(x = pobl / 1000000, y = factor(ranking), label = pais)) +
scale_fill_manual(values = color_continentes) +
theme_minimal() +
transition_time(año) +
labs(title = "Año: {frame_time}", y = NULL, x = "población (millones)")
gapminder_anim1
Por último, un detalle más: vean que en la primera línea agregamos
aes(group = pais). Esto sirve para que
gganimate entienda que los valores de la esa variable
representan a la misma entidad a lo largo de los años, haciendo que
cuando un pais gana o pierde puestos esto se refleje en un
desplazamiento de su barra. Para ver lo que pasaría si no agregáramos
aes(group = pais), ejecuten el código borrando esa parte y
revisen los resultados.
Vamos a terminar con nuestra variante de la visualización que Has Rosling llamaba “un mundo en convergencia”: el gradual cierre de la enorme brecha que separaba a las principales naciones occidentales industrializadas del resto del mundo.
Recuperemos una vez más nuestro gráfico gapmindereano:
ggplot(filter(gapminder, año == 2007),
aes(x = PBIpc, y = expVida, size = pobl/1000000, color = continente)) +
geom_point() +
scale_x_log10() +
scale_colour_manual(values = color_continentes) +
guides(size = "none") +
theme_minimal() +
labs(title = "Riqueza vs. salud en los países del mundo", subtitle = "según datos 2007",
size = "población (millones)",
x = "PBI per capita (USD)", y = "expectativa de vida en años",
caption = "fuente: Gapminder, www.gapminder.com")
Bien, aplicando los mismos ajustes que realizamos para animar la
carrera de barras, podemos obtener la versión dinámica. Esta vamos a
usar todo el dataset, ya que nos interesa tener las mediciones en
distintos años. O sea, en lugar del dataset modificado con
filter(gapminder, año == 2007) lo vamos a usar completo,
sin aplicarle filtros.
ggplot(gapminder,
aes(x = PBIpc, y = expVida, size = pobl/1000000, color = continente)) +
geom_point() +
scale_x_log10() +
scale_colour_manual(values = color_continentes) +
guides(size = "none") +
theme_minimal() +
labs(title = "Riqueza vs. salud en los países del mundo", subtitle = "según datos {frame_time}",
size = "población (millones)",
x = "PBI per capita (USD)", y = "expectativa de vida en años",
caption = "fuente: Gapminder, www.gapminder.com") +
transition_time(año)
Y con eso terminamos esta breve introducción a las visualizaciones
animadas. Para continuar explorando las muchísimas opciones disponibles
con gganimate() el mejor mejor recurso -al momento de
escribir estas líneas- es el sitio
oficial del paquete.
Para continuar aprendiendo sobre visualizaciones interactivas, se puede acceder en forma gratuita al contenido del libro Interactive web-based data visualization with R, plotly, and shiny” de Carson Sievert.
¡Hasta la próxima!